/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.devs.benchmark.topology;

import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.devs.benchmark.Util;
import org.devs.benchmark.modeling.AtomicType;
import org.devs.benchmark.modeling.ConnectionType;
import org.devs.benchmark.modeling.CoupledType;
import org.devs.benchmark.modeling.ModelType;
import org.devs.benchmark.modeling.ObjectFactory;

/**
 *
 * @author Alejandro Moreno
 */
public abstract class Network {

    protected ModelType model;
    String name;
    int recursion;
    Class message;

    public Network(String name, Class message, int recursion) {
        this.name = name;
        this.message = message;
        this.recursion = recursion;
    }

    public Network(String name, Class message) {
        this.name = name;
        this.message = message;
        this.recursion = 1;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public ModelType getModel() {
        return model;
    }

    public void setModel(ModelType model) {
        this.model = model;
    }

    public Class getMessage() {
        return message;
    }

    public void setMessage(Class message) {
        this.message = message;
    }

    public int getRecursion() {
        return recursion;
    }

    public void setRecursion(int recursion) {
        this.recursion = recursion;
    }


    public CoupledType buildNode(String name,int num, Class message, String id) {

        CoupledType coupled = new CoupledType(name + "_Coupled" + id, Util.createInports(num, message), Util.createOutports(num, message));
        AtomicType atomic = new AtomicType(name + "_Atomic_" + id, Util.createDhrystoneParameters(), Util.createInports(num, message), Util.createOutports(num, message));
        
        coupled.getModels().add(atomic);
        addConnections(coupled, atomic);
        return coupled;

    }

    public void addPorts(CoupledType coupled, int index, String name, int num, Class message){

        coupled.getInports().addAll(Util.createInports(name+"_in", num, message));
        coupled.getOutports().addAll(Util.createOutports(name+"_out", num, message));
        
        ModelType sub = coupled.getModels().get(index);

        sub.getInports().addAll(Util.createInports(name+"_in", num, message));
        sub.getOutports().addAll(Util.createOutports(name+"_out", num, message));

        for(int i=0;i<num;i++){
            addConnectionFromParent(coupled, index, i, i);
            addConnectionToParent(coupled, index, i, i);
        }

    }

    public boolean isEndNode(ModelType mod){

        if(((CoupledType)mod).getModels().get(0) instanceof AtomicType)
            return true;
        else return false;

    }

    
    public void addConnection(CoupledType coupled, int modelFrom, int portFrom, int modelTo, int portTo) {

        List<ModelType> models = coupled.getModels();
        coupled.getConnections().add(new ConnectionType(models.get(modelFrom), models.get(modelFrom).getOutports().get(portFrom), models.get(modelTo), models.get(modelTo).getInports().get(portTo)));        

    }

    public void addConnectionToParent(CoupledType coupled, int modelFrom, int portFrom, int portTo) {

        List<ModelType> models = coupled.getModels();
        coupled.getConnections().add(new ConnectionType(models.get(modelFrom), models.get(modelFrom).getOutports().get(portFrom), coupled, coupled.getOutports().get(portTo)));

    }

    public void addConnectionFromParent(CoupledType coupled, int modelTo, int portTo, int portFrom) {

        List<ModelType> models = coupled.getModels();
        coupled.getConnections().add(new ConnectionType( coupled, coupled.getInports().get(portFrom),models.get(modelTo), models.get(modelTo).getInports().get(portTo)));

    }

    public void addConnections(CoupledType coupled, AtomicType atomic, int port) {

        coupled.getConnections().add(new ConnectionType(coupled, coupled.getInports().get(port), atomic, atomic.getInports().get(0)));

        for (int i = 0; i < coupled.getOutports().size(); i++) {

            if (i != port) {
                coupled.getConnections().add(new ConnectionType(atomic, atomic.getOutports().get(0), coupled, coupled.getOutports().get(i)));
            }


        }

    }

    public void addConnections(CoupledType coupled, AtomicType atomic) {

        for (int i = 0; i < coupled.getOutports().size(); i++) {

            coupled.getConnections().add(new ConnectionType(atomic, atomic.getOutports().get(i), coupled, coupled.getOutports().get(i)));

        }

    }


    public abstract void buildModel();

    ///////////////////////////////////////////////////////////////////////////777

    public static String addGraphEnvelope(String name, String message) {

        return "digraph " + name + " {\n\tconcentrate=true;\n\trankdir=LR;\n" + message + "\n}\n";

    }

    public static String addSubGraphEnvelope(String name, String message, int index) {

        String indent = getIndent(index);
        String dot = indent + "subgraph cluster" + name + " {\n";
        dot += indent + "\tlabel = \"" + name + "\";\n";
        return dot + message + "\n" + indent + "}\n";

    }

    public static String getIndent(int index) {
        String indent = "";

        for (int i = 0; i < index; i++) {
            indent += "\t";
        }

        return indent;
    }

    public String toDot() {

        return toDot((CoupledType) this.model);
    }

    public String cToDot(CoupledType coupled) {

        List<ModelType> models = coupled.getModels();
        List<ConnectionType> connections = coupled.getConnections();
        String dot = "";

        dot += "\tnode [shape = circle];\n";

        for (int i = 0; i < models.size(); i++) {
            if (((CoupledType) coupled.getModels().get(i)).getModels().get(0) instanceof AtomicType) {
                dot += "\t" + models.get(i).getName() + " [style=filled];\n";
            }
        }

        dot += "\tnode [shape = doublecircle];\n";

        for (int i = 0; i < connections.size(); i++) {
            //dot += "\t" + connections.get(i).getModelFrom() + " -> " + connections.get(i).getModelTo() + "[label =\""+connections.get(i).getPortFrom()+"-"+connections.get(i).getPortTo()+"\", style = bold];\n";
            dot += "\t" + connections.get(i).getModelFrom() + " -> " + connections.get(i).getModelTo() + "[style = bold];\n";
        }

        return dot;

    }

    public String toDot(CoupledType coupled) {

        return addGraphEnvelope(coupled.getName(), cToDot(coupled));

    }

    public String mToDot(CoupledType coupled) {

        String dot = cToDot(coupled);

        for (int i = 0; i < coupled.getModels().size(); i++) {

            if (((CoupledType) coupled.getModels().get(i)).getModels().get(0) instanceof CoupledType) {
                dot += mToDot((CoupledType) coupled.getModels().get(i));
            }

        }

        return dot;

    }

    public String mgToDot(CoupledType coupled) {

        return addGraphEnvelope(coupled.getName(), mToDot(coupled));
    }

    public String toDot(CoupledType coupled, int... index) {

        CoupledType coup = coupled;

        try {
            for (int i = 0; i < index.length; i++) {
                coup = (CoupledType) coup.getModels().get(index[i]);
            }
        } catch (ArrayIndexOutOfBoundsException ex) {
            return "";
        }

        return toDot(coup);

    }

    public String toDot(int... index) {

        return toDot((CoupledType) this.model, index);

    }

    public void toDot(CoupledType model, String url) {
        try {
            BufferedWriter out = null;
            out = new BufferedWriter(new FileWriter(url));
            out.write(toDot(model));
            out.close();
        } catch (IOException ex) {
            Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void mgToDot(CoupledType model, String url) {
        try {
            BufferedWriter out = null;
            out = new BufferedWriter(new FileWriter(url));
            out.write(mgToDot(model));
            out.close();
        } catch (IOException ex) {
            Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void toDot(CoupledType model, String url, int... index) {
        try {
            BufferedWriter out = null;
            out = new BufferedWriter(new FileWriter(url));
            out.write(toDot(model, index));
            out.close();
        } catch (IOException ex) {
            Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void toDot(String url) {
        toDot((CoupledType) this.model, url);
    }

    public void mgToDot(String url) {
        mgToDot((CoupledType) this.model, url);
    }

    public void toDot(String url, int... index) {
        toDot((CoupledType) this.model, url, index);
    }

    public void toXML(String url) {
        toXML(this.model, url);
    }

    public void toXML(ModelType model, String url) {
        try {
            JAXBContext jaxbContext = JAXBContext.newInstance("org.devs.benchmark.modeling");
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, new Boolean(true));
            ObjectFactory factory = new ObjectFactory();
            marshaller.marshal(factory.createModel(model), new FileOutputStream(url));
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex);
        } catch (JAXBException ex) {
            Logger.getLogger(Network.class.getName()).log(Level.SEVERE, null, ex);
        }

    }
}
